// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// Objective C support is only available on mac.
@TestOn('mac-os')
import 'dart:ffi';
import 'dart:io';

import 'package:objective_c/objective_c.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';

import '../test_utils.dart';
import 'global_native_bindings.dart';
import 'util.dart';

void main() {
  group('global using @Native', () {
    setUpAll(() {
      final dylib = File(
        path.join(
          packagePathForTests,
          'test',
          'native_objc_test',
          'objc_test.dylib',
        ),
      );
      verifySetupFile(dylib);
      DynamicLibrary.open(dylib.absolute.path);
      generateBindingsForCoverage('global');
    });

    test('Global string', () {
      expect(globalString.toDartString(), 'Hello World');
      globalString = 'Something else'.toNSString();
      expect(globalString.toDartString(), 'Something else');
      globalString = 'Hello World'.toNSString();
    });

    Pointer<ObjCObjectImpl> globalObjectRefCountingInner() {
      globalObject = NSObject();
      final obj1raw = globalObject!.ref.pointer;

      expect(objectRetainCount(obj1raw), greaterThan(0));

      return obj1raw;
    }

    test('Global object ref counting', () {
      final obj1raw = globalObjectRefCountingInner();
      globalObject = null;
      doGC();
      expect(objectRetainCount(obj1raw), 0);
    }, skip: !canDoGC);

    test('Global block', () {
      globalBlock = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10);
      expect(globalBlock!(123), 1230);
      globalBlock = ObjCBlock_Int32_Int32.fromFunction((int x) => x + 1000);
      expect(globalBlock!(456), 1456);
    });

    (Pointer<ObjCBlockImpl>, Pointer<ObjCBlockImpl>)
    globalBlockRefCountingInner() {
      final blk1 = ObjCBlock_Int32_Int32.fromFunction((int x) => x * 10);
      globalBlock = blk1;
      final blk1raw = blk1.ref.pointer;
      expect(blockRetainCount(blk1raw), 2); // blk1, and the global variable.

      final blk2 = ObjCBlock_Int32_Int32.fromFunction((int x) => x + 1000);
      globalBlock = blk2;
      final blk2raw = blk2.ref.pointer;
      expect(blockRetainCount(blk2raw), 2); // blk2, and the global variable.
      expect(blockRetainCount(blk1raw), 1); // Just blk1.
      expect(blk1, isNotNull); // Force blk1 to stay in scope.
      expect(blk2, isNotNull); // Force blk2 to stay in scope.

      return (blk1raw, blk2raw);
    }

    test('Global block ref counting', () {
      final (blk1raw, blk2raw) = globalBlockRefCountingInner();
      doGC();

      expect(blockRetainCount(blk2raw), 1); // Just the global variable.
      expect(blockRetainCount(blk1raw), 0);

      globalBlock = null;
      expect(blockRetainCount(blk2raw), 0);
      expect(blockRetainCount(blk1raw), 0);
    }, skip: !canDoGC);
  });
}
